import math
import time
from datetime import datetime, timedelta

from 布林策略实盘.config import *

from 布林策略实盘.utils.commons import retry_wrapper
import pandas as pd

from 布林策略实盘.utils.dingding import send_dingding_msg


# ===获取现货、U本位合约、币本位合约交易币对价格精度和数量精度
def fetch_symbol_precision_info(symbol, trade_type):
    exchange_info = []
    # 现货
    if trade_type == ('spot' or 'SPOT'):
        # 获取现货交易规则和交易对
        # GET /fapi/v1/exchangeInfo
        exchange_info = exchange.publicGetExchangeInfo()
    # U本位合约
    elif trade_type == ('usdt' or 'USDT'):
        # 获取u本位合约交易规则和交易对
        # GET /fapi/v1/exchangeInfo
        exchange_info = exchange.fapiPublicGetExchangeInfo()
    # 币本位合约
    elif trade_type == ('coin' or 'COIN'):
        # 获取币本位合约交易规则和交易对
        # GET /dapi/v1/exchangeInfo
        exchange_info = exchange.dapiPublicGetExchangeInfo()
    df = pd.DataFrame(exchange_info['symbols'])
    # 排除历史数据中有，但是交易所中已经被下架的交易对
    if df['symbol'].values.__contains__(symbol):
        filters = pd.DataFrame(df.loc[df['symbol'] == symbol, 'filters'].iloc[0])
        tick_size = float(filters.loc[filters['filterType'] == 'PRICE_FILTER', 'tickSize'].iloc[0])
        price_precision = int(math.log(tick_size, 0.1))
        step_size = float(filters.loc[filters['filterType'] == 'LOT_SIZE', 'stepSize'].iloc[0])
        quantity_precision = int(math.log(step_size, 0.1))
        return price_precision, quantity_precision
    else:
        raise ValueError(f'{trade_type}的交易类型中，{symbol}交易对已被交易所下架，无法查询到精度信息')


# 缓存交易币对精度信息
def fetch_symbol_config_precision_info():
    for symbol in symbol_config.keys():
        tick_size, step_size = fetch_symbol_precision_info(symbol, 'usdt')
        symbol_config[symbol]['最小下单价精度'] = tick_size
        symbol_config[symbol]['最小下单量精度'] = step_size


# ===获取币种历史数据===
def fetch_candle_data(candle_num):
    symbol_candle_data = dict()

    for symbol in symbol_config:
        symbol_params = {
            'symbol': symbol,
            'interval': symbol_config[symbol]['time_interval'],
            'limit': candle_num,
        }

        # K线数据
        # GET /fapi/v1/klines
        response = retry_wrapper(exchange.fapiPublicGetKlines, params=symbol_params, func_name='获取币种K线数据', )

        df = pd.DataFrame(response, dtype=float)
        # 对数据进行整理
        df.rename(columns={0: 'open_time', 1: 'open', 2: 'high', 3: 'low', 4: 'close', 5: 'volume'},
                  inplace=True)
        df['open_time'] = pd.to_datetime(df['open_time'], unit='ms')
        df['candle_begin_time_GMT8'] = df['open_time'] + timedelta(hours=8)
        df = df[['candle_begin_time_GMT8', 'open', 'high', 'low', 'close', 'volume']]
        symbol_candle_data[symbol] = df

    return symbol_candle_data


# 串行获取K线数据
def single_threading_get_candle_data(account_info, run_time, candle_num, if_print=True):
    """
    串行逐个获取所有交易对的K线数据，速度较慢
    若获取数据失败，返回空的dataframe。
    """
    symbol_lasted_candle_data = dict()

    # 函数返回的变量
    account_info['当前价格'] = None
    for symbol in symbol_config:
        print(f'获取{symbol}的K线\n', f'  开始时间：{datetime.now()},', end=' ')

        # 逐个获取symbol对应的K线数据
        df = fetch_candle_data(candle_num)[symbol]
        print(f'结束时间：{datetime.now()}')
        account_info.at[symbol, '当前价格'] = df.iloc[-1]['close']  # 该品种的最新价格
        symbol_lasted_candle_data[symbol] = df[
            df['candle_begin_time_GMT8'] < pd.to_datetime(run_time)]  # 去除run_time周期的数据

    if if_print:
        for symbol in symbol_config.keys():
            print(f'\n{symbol}最新的k线数据为\n', symbol_lasted_candle_data[symbol])
    return symbol_lasted_candle_data


# 合并历史k线数据和最新的k线数据
def merge_symbol_candle_data(symbol_candle_data, symbol_lasted_candle_data):
    for symbol in symbol_config:
        # 将symbol_candle_data和最新获取的symbol_lasted_candle_data数据合并
        df = symbol_candle_data[symbol]._append(symbol_lasted_candle_data[symbol], ignore_index=True)
        df.drop_duplicates(subset=['candle_begin_time_GMT8'], keep='last', inplace=True)
        df.sort_values(by='candle_begin_time_GMT8', inplace=True)  # 排序，理论上这步应该可以省略，加快速度
        df = df.iloc[-max_len:]  # 保持最大K线数量不会超过max_len个
        df.reset_index(drop=True, inplace=True)
        symbol_candle_data[symbol] = df
    return symbol_candle_data


# 获取当前资金费率及账户余额
def calcutate_funding_rate(account_info):
    t1 = datetime.now().hour
    t2 = datetime.now().minute
    # 测试数据
    # import datetime
    # t1 = datetime.time(1, 0).hour
    # t2 = datetime.time(1, 0).minute
    # 提前7h报送数据
    if t1 % 8 == 1 and t2 == 0:
        next_funding_time = None
        funding_rate_loss_sum = 0
        for symbol in account_info.index:
            if (account_info.at[symbol, '持仓量']) != 0:
                # GET /fapi/v1/fundingRate
                result = retry_wrapper(
                    exchange.fapiPublicGetPremiumIndex, params={'symbol': symbol}, func_name='查看持仓币种资金费率')
                funding_rate = float(result.get('lastFundingRate'))
                _ = float(result.get('nextFundingTime')) / 1000
                next_funding_time = time.strftime("%H:%M:%S", time.localtime(_))
                dir = account_info.at[symbol, '持仓方向']
                # 计算账户资金资金费率损益
                symbol_equity = account_info.at[symbol, '分配资金']
                funding_rate_loss = symbol_equity * - funding_rate * dir
                funding_rate_loss_sum += funding_rate_loss
                print(f'{symbol}下次的资金费率为：{funding_rate * 100:.2f}%，预计损益为：{funding_rate_loss:.2f}')
        print(f'下次资金费率时间为：{next_funding_time}，预计总损益为：{funding_rate_loss_sum:.2f}\n')
        if not next_funding_time is None:
            send_dingding_msg(
                f'下次资金费率时间为：{next_funding_time}\n预计资金费率总损益为：{funding_rate_loss_sum:.2f}\n')
